home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / tcl / dist6.3 / tclGlob.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-02-10  |  14.1 KB  |  546 lines

  1. /* 
  2.  * tclGlob.c --
  3.  *
  4.  *    This file provides procedures and commands for file name
  5.  *    manipulation, such as tilde expansion and globbing.
  6.  *
  7.  * Copyright 1990-1991 Regents of the University of California
  8.  * Permission to use, copy, modify, and distribute this
  9.  * software and its documentation for any purpose and without
  10.  * fee is hereby granted, provided that the above copyright
  11.  * notice appear in all copies.  The University of California
  12.  * makes no representations about the suitability of this
  13.  * software for any purpose.  It is provided "as is" without
  14.  * express or implied warranty.
  15.  */
  16.  
  17. #ifndef lint
  18. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclGlob.c,v 1.22 92/02/10 08:35:46 ouster Exp $ SPRITE (Berkeley)";
  19. #endif /* not lint */
  20.  
  21. #include "tclInt.h"
  22. #include "tclUnix.h"
  23.  
  24. /*
  25.  * The structure below is used to keep track of a globbing result
  26.  * being built up (i.e. a partial list of file names).  The list
  27.  * grows dynamically to be as big as needed.
  28.  */
  29.  
  30. typedef struct {
  31.     char *result;        /* Pointer to result area. */
  32.     int totalSpace;        /* Total number of characters allocated
  33.                  * for result. */
  34.     int spaceUsed;        /* Number of characters currently in use
  35.                  * to hold the partial result (not including
  36.                  * the terminating NULL). */
  37.     int dynamic;        /* 0 means result is static space, 1 means
  38.                  * it's dynamic. */
  39. } GlobResult;
  40.  
  41. /*
  42.  * Declarations for procedures local to this file:
  43.  */
  44.  
  45. static void        AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
  46.                 char *dir, char *name, int nameLength));
  47. static int        DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
  48.                 char *rem));
  49.  
  50. /*
  51.  *----------------------------------------------------------------------
  52.  *
  53.  * AppendResult --
  54.  *
  55.  *    Given two parts of a file name (directory and element within
  56.  *    directory), concatenate the two together and append them to
  57.  *    the result building up in interp.
  58.  *
  59.  * Results:
  60.  *    There is no return value.
  61.  *
  62.  * Side effects:
  63.  *    Interp->result gets extended.
  64.  *
  65.  *----------------------------------------------------------------------
  66.  */
  67.  
  68. static void
  69. AppendResult(interp, dir, name, nameLength)
  70.     Tcl_Interp *interp;        /* Interpreter whose result should be
  71.                  * appended to. */
  72.     char *dir;            /* Name of directory, with trailing
  73.                  * slash (unless the whole string is
  74.                  * empty). */
  75.     char *name;            /* Name of file withing directory (NOT
  76.                  * necessarily null-terminated!). */
  77.     int nameLength;        /* Number of characters in name. */
  78. {
  79.     int dirLength, dirFlags, nameFlags;
  80.     char *p, saved;
  81.  
  82.     /*
  83.      * Next, see if we can put together a valid list element from dir
  84.      * and name by calling Tcl_AppendResult.
  85.      */
  86.  
  87.     if (*dir == 0) {
  88.     dirFlags = 0;
  89.     } else {
  90.     Tcl_ScanElement(dir, &dirFlags);
  91.     }
  92.     saved = name[nameLength];
  93.     name[nameLength] = 0;
  94.     Tcl_ScanElement(name, &nameFlags);
  95.     if ((dirFlags == 0) && (nameFlags == 0)) {
  96.     if (*interp->result != 0) {
  97.         Tcl_AppendResult(interp, " ", dir, name, (char *) NULL);
  98.     } else {
  99.         Tcl_AppendResult(interp, dir, name, (char *) NULL);
  100.     }
  101.     name[nameLength] = saved;
  102.     return;
  103.     }
  104.  
  105.     /*
  106.      * This name has weird characters in it, so we have to convert it to
  107.      * a list element.  To do that, we have to merge the characters
  108.      * into a single name.  To do that, malloc a buffer to hold everything.
  109.      */
  110.  
  111.     dirLength = strlen(dir);
  112.     p = (char *) ckalloc((unsigned) (dirLength + nameLength + 1));
  113.     strcpy(p, dir);
  114.     strcpy(p+dirLength, name);
  115.     name[nameLength] = saved;
  116.     Tcl_AppendElement(interp, p, 0);
  117.     ckfree(p);
  118. }
  119.  
  120. /*
  121.  *----------------------------------------------------------------------
  122.  *
  123.  * DoGlob --
  124.  *
  125.  *    This recursive procedure forms the heart of the globbing
  126.  *    code.  It performs a depth-first traversal of the tree
  127.  *    given by the path name to be globbed.
  128.  *
  129.  * Results:
  130.  *    The return value is a standard Tcl result indicating whether
  131.  *    an error occurred in globbing.  After a normal return the
  132.  *    result in interp will be set to hold all of the file names
  133.  *    given by the dir and rem arguments.  After an error the
  134.  *    result in interp will hold an error message.
  135.  *
  136.  * Side effects:
  137.  *    None.
  138.  *
  139.  *----------------------------------------------------------------------
  140.  */
  141.  
  142. static int
  143. DoGlob(interp, dir, rem)
  144.     Tcl_Interp *interp;            /* Interpreter to use for error
  145.                      * reporting (e.g. unmatched brace). */
  146.     char *dir;                /* Name of a directory at which to
  147.                      * start glob expansion.  This name
  148.                      * is fixed: it doesn't contain any
  149.                      * globbing chars.  If it's non-empty
  150.                      * then it should end with a slash. */
  151.     char *rem;                /* Path to glob-expand. */
  152. {
  153.     /*
  154.      * When this procedure is entered, the name to be globbed may
  155.      * already have been partly expanded by ancestor invocations of
  156.      * DoGlob.  The part that's already been expanded is in "dir"
  157.      * (this may initially be empty), and the part still to expand
  158.      * is in "rem".  This procedure expands "rem" one level, making
  159.      * recursive calls to itself if there's still more stuff left
  160.      * in the remainder.
  161.      */
  162.  
  163.     register char *p;
  164.     register char c;
  165.     char *openBrace, *closeBrace;
  166.     int gotSpecial, result;
  167.  
  168.     /*
  169.      * When generating information for the next lower call,
  170.      * use static areas if the name is short, and malloc if the name
  171.      * is longer.
  172.      */
  173.  
  174. #define STATIC_SIZE 200
  175.  
  176.     /*
  177.      * First, find the end of the next element in rem, checking
  178.      * along the way for special globbing characters.
  179.      */
  180.  
  181.     gotSpecial = 0;
  182.     openBrace = closeBrace = NULL;
  183.     for (p = rem; ; p++) {
  184.     c = *p;
  185.     if ((c == '\0') || (c == '/')) {
  186.         break;
  187.     }
  188.     if ((c == '{') && (openBrace == NULL)) {
  189.         openBrace = p;
  190.     }
  191.     if ((c == '}') && (closeBrace == NULL)) {
  192.         closeBrace = p;
  193.     }
  194.     if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
  195.         gotSpecial = 1;
  196.     }
  197.     }
  198.  
  199.     /*
  200.      * If there is an open brace in the argument, then make a recursive
  201.      * call for each element between the braces.  In this case, the
  202.      * recursive call to DoGlob uses the same "dir" that we got.
  203.      * If there are several brace-pairs in a single name, we just handle
  204.      * one here, and the others will be handled in recursive calls.
  205.      */
  206.  
  207.     if (openBrace != NULL) {
  208.     int remLength, l1, l2;
  209.     char static1[STATIC_SIZE];
  210.     char *element, *newRem;
  211.  
  212.     if (closeBrace == NULL) {
  213.         Tcl_ResetResult(interp);
  214.         interp->result = "unmatched open-brace in file name";
  215.         return TCL_ERROR;
  216.     }
  217.     remLength = strlen(rem) + 1;
  218.     if (remLength <= STATIC_SIZE) {
  219.         newRem = static1;
  220.     } else {
  221.         newRem = (char *) ckalloc((unsigned) remLength);
  222.     }
  223.     l1 = openBrace-rem;
  224.     strncpy(newRem, rem, l1);
  225.     p = openBrace;
  226.     for (p = openBrace; *p != '}'; ) {
  227.         element = p+1;
  228.         for (p = element; ((*p != '}') && (*p != ',')); p++) {
  229.         /* Empty loop body:  just find end of this element. */
  230.         }
  231.         l2 = p - element;
  232.         strncpy(newRem+l1, element, l2);
  233.         strcpy(newRem+l1+l2, closeBrace+1);
  234.         if (DoGlob(interp, dir, newRem) != TCL_OK) {
  235.         return TCL_ERROR;
  236.         }
  237.     }
  238.     if (remLength > STATIC_SIZE) {
  239.         ckfree(newRem);
  240.     }
  241.     return TCL_OK;
  242.     }
  243.  
  244.     /*
  245.      * If there were any pattern-matching characters, then scan through
  246.      * the directory to find all the matching names.
  247.      */
  248.  
  249.     if (gotSpecial) {
  250.     DIR *d;
  251.     struct dirent *entryPtr;
  252.     int l1, l2;
  253.     char *pattern, *newDir, *dirName;
  254.     char static1[STATIC_SIZE], static2[STATIC_SIZE];
  255.     struct stat statBuf;
  256.  
  257.     /*
  258.      * Be careful not to do any actual file system operations on a
  259.      * directory named "";  instead, use ".".  This is needed because
  260.      * some versions of UNIX don't treat "" like "." automatically.
  261.      */
  262.  
  263.     if (*dir == '\0') {
  264.         dirName = ".";
  265.     } else {
  266.         dirName = dir;
  267.     }
  268.     if ((stat(dirName, &statBuf) != 0)
  269.         || ((statBuf.st_mode & S_IFMT) != S_IFDIR)) {
  270.         return TCL_OK;
  271.     }
  272.     d = opendir(dirName);
  273.     if (d == NULL) {
  274.         Tcl_ResetResult(interp);
  275.         Tcl_AppendResult(interp, "couldn't read directory \"",
  276.             dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
  277.         return TCL_ERROR;
  278.     }
  279.     l1 = strlen(dir);
  280.     l2 = (p - rem);
  281.     if (l2 < STATIC_SIZE) {
  282.         pattern = static2;
  283.     } else {
  284.         pattern = (char *) ckalloc((unsigned) (l2+1));
  285.     }
  286.     strncpy(pattern, rem, l2);
  287.     pattern[l2] = '\0';
  288.     result = TCL_OK;
  289.     while (1) {
  290.         entryPtr = readdir(d);
  291.         if (entryPtr == NULL) {
  292.         break;
  293.         }
  294.  
  295.         /*
  296.          * Don't match names starting with "." unless the "." is
  297.          * present in the pattern.
  298.          */
  299.  
  300.         if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
  301.         continue;
  302.         }
  303.         if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
  304.         int nameLength = strlen(entryPtr->d_name);
  305.  
  306.         if (*p == 0) {
  307.             AppendResult(interp, dir, entryPtr->d_name, nameLength);
  308.         } else {
  309.             if ((l1+nameLength+2) <= STATIC_SIZE) {
  310.             newDir = static1;
  311.             } else {
  312.             newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
  313.             }
  314.             sprintf(newDir, "%s%s/", dir, entryPtr->d_name);
  315.             result = DoGlob(interp, newDir, p+1);
  316.             if (newDir != static1) {
  317.             ckfree(newDir);
  318.             }
  319.             if (result != TCL_OK) {
  320.             break;
  321.             }
  322.         }
  323.         }
  324.     }
  325.     closedir(d);
  326.     if (pattern != static2) {
  327.         ckfree(pattern);
  328.     }
  329.     return result;
  330.     }
  331.  
  332.     /*
  333.      * This is the simplest case:  just another path element.  Move
  334.      * it to the dir side and recurse (or just add the name to the
  335.      * list, if we're at the end of the path).
  336.      */
  337.  
  338.     if (*p == 0) {
  339.     AppendResult(interp, dir, rem, p-rem);
  340.     } else {
  341.     int l1, l2;
  342.     char *newDir;
  343.     char static1[STATIC_SIZE];
  344.  
  345.     l1 = strlen(dir);
  346.     l2 = l1 + (p - rem) + 2;
  347.     if (l2 <= STATIC_SIZE) {
  348.         newDir = static1;
  349.     } else {
  350.         newDir = (char *) ckalloc((unsigned) l2);
  351.     }
  352.     strcpy(newDir, dir);
  353.     strncpy(newDir+l1, rem, p-rem);
  354.     newDir[l2-2] = '/';
  355.     newDir[l2-1] = 0;
  356.     result = DoGlob(interp, newDir, p+1);
  357.     if (newDir != static1) {
  358.         ckfree(newDir);
  359.     }
  360.     if (result != TCL_OK) {
  361.         return TCL_ERROR;
  362.     }
  363.     }
  364.     return TCL_OK;
  365. }
  366.  
  367. /*
  368.  *----------------------------------------------------------------------
  369.  *
  370.  * Tcl_TildeSubst --
  371.  *
  372.  *    Given a name starting with a tilde, produce a name where
  373.  *    the tilde and following characters have been replaced by
  374.  *    the home directory location for the named user.
  375.  *
  376.  * Results:
  377.  *    The result is a pointer to a static string containing
  378.  *    the new name.  This name will only persist until the next
  379.  *    call to Tcl_TildeSubst;  save it if you care about it for
  380.  *    the long term.  If there was an error in processing the
  381.  *    tilde, then an error message is left in interp->result
  382.  *    and the return value is NULL.
  383.  *
  384.  * Side effects:
  385.  *    None that the caller needs to worry about.
  386.  *
  387.  *----------------------------------------------------------------------
  388.  */
  389.  
  390. char *
  391. Tcl_TildeSubst(interp, name)
  392.     Tcl_Interp *interp;        /* Interpreter in which to store error
  393.                  * message (if necessary). */
  394.     char *name;            /* File name, which may begin with "~/"
  395.                  * (to indicate current user's home directory)
  396.                  * or "~<user>/" (to indicate any user's
  397.                  * home directory). */
  398. {
  399. #define STATIC_BUF_SIZE 50
  400.     static char staticBuf[STATIC_BUF_SIZE];
  401.     static int curSize = STATIC_BUF_SIZE;
  402.     static char *curBuf = staticBuf;
  403.     char *dir;
  404.     int length;
  405.     int fromPw = 0;
  406.     register char *p;
  407.  
  408.     if (name[0] != '~') {
  409.     return name;
  410.     }
  411.  
  412.     /*
  413.      * First, find the directory name corresponding to the tilde entry.
  414.      */
  415.  
  416.     if ((name[1] == '/') || (name[1] == '\0')) {
  417.     dir = getenv("HOME");
  418.     if (dir == NULL) {
  419.         Tcl_ResetResult(interp);
  420.         Tcl_AppendResult(interp, "couldn't find HOME environment ",
  421.             "variable to expand \"", name, "\"", (char *) NULL);
  422.         return NULL;
  423.     }
  424.     p = name+1;
  425.     } else {
  426.     struct passwd *pwPtr;
  427.  
  428.     for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
  429.         /* Null body;  just find end of name. */
  430.     }
  431.     length = p-&name[1];
  432.     if (length >= curSize) {
  433.         length = curSize-1;
  434.     }
  435.     memcpy((VOID *) curBuf, (VOID *) (name+1), length);
  436.     curBuf[length] = '\0';
  437.     pwPtr = getpwnam(curBuf);
  438.     if (pwPtr == NULL) {
  439.         Tcl_ResetResult(interp);
  440.         Tcl_AppendResult(interp, "user \"", curBuf,
  441.             "\" doesn't exist", (char *) NULL);
  442.         return NULL;
  443.     }
  444.     dir = pwPtr->pw_dir;
  445.     fromPw = 1;
  446.     }
  447.  
  448.     /*
  449.      * Grow the buffer if necessary to make enough space for the
  450.      * full file name.
  451.      */
  452.  
  453.     length = strlen(dir) + strlen(p);
  454.     if (length >= curSize) {
  455.     if (curBuf != staticBuf) {
  456.         ckfree(curBuf);
  457.     }
  458.     curSize = length + 1;
  459.     curBuf = (char *) ckalloc((unsigned) curSize);
  460.     }
  461.  
  462.     /*
  463.      * Finally, concatenate the directory name with the remainder
  464.      * of the path in the buffer.
  465.      */
  466.  
  467.     strcpy(curBuf, dir);
  468.     strcat(curBuf, p);
  469.     if (fromPw) {
  470.     endpwent();
  471.     }
  472.     return curBuf;
  473. }
  474.  
  475. /*
  476.  *----------------------------------------------------------------------
  477.  *
  478.  * Tcl_GlobCmd --
  479.  *
  480.  *    This procedure is invoked to process the "glob" Tcl command.
  481.  *    See the user documentation for details on what it does.
  482.  *
  483.  * Results:
  484.  *    A standard Tcl result.
  485.  *
  486.  * Side effects:
  487.  *    See the user documentation.
  488.  *
  489.  *----------------------------------------------------------------------
  490.  */
  491.  
  492.     /* ARGSUSED */
  493. int
  494. Tcl_GlobCmd(dummy, interp, argc, argv)
  495.     ClientData dummy;            /* Not used. */
  496.     Tcl_Interp *interp;            /* Current interpreter. */
  497.     int argc;                /* Number of arguments. */
  498.     char **argv;            /* Argument strings. */
  499. {
  500.     int i, result, noComplain;
  501.  
  502.     if (argc < 2) {
  503.     notEnoughArgs:
  504.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  505.         " ?-nocomplain? name ?name ...?\"", (char *) NULL);
  506.     return TCL_ERROR;
  507.     }
  508.     noComplain = 0;
  509.     if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
  510.     if (argc < 3) {
  511.         goto notEnoughArgs;
  512.     }
  513.     noComplain = 1;
  514.     }
  515.  
  516.     for (i = 1 + noComplain; i < argc; i++) {
  517.     char *thisName;
  518.  
  519.     /*
  520.      * Do special checks for names starting at the root and for
  521.      * names beginning with ~.  Then let DoGlob do the rest.
  522.      */
  523.  
  524.     thisName = argv[i];
  525.     if (*thisName == '~') {
  526.         thisName = Tcl_TildeSubst(interp, thisName);
  527.         if (thisName == NULL) {
  528.         return TCL_ERROR;
  529.         }
  530.     }
  531.     if (*thisName == '/') {
  532.         result = DoGlob(interp, "/", thisName+1);
  533.     } else {
  534.         result = DoGlob(interp, "", thisName);
  535.     }
  536.     if (result != TCL_OK) {
  537.         return result;
  538.     }
  539.     }
  540.     if ((*interp->result == 0) && !noComplain) {
  541.     interp->result = "no files matched glob pattern(s)";
  542.     return TCL_ERROR;
  543.     }
  544.     return TCL_OK;
  545. }
  546.